package com.gobang.websocket.service;

import com.gobang.common.constant.BusinessErrorCode;
import com.gobang.common.exception.BusinessException;
import com.gobang.util.RedisUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

/**
 * 断线重连令牌服务
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class ReconnectTokenService {

    /**
     * 重连令牌 TTL（秒）- 2 分钟
     */
    private static final long RECONNECT_TOKEN_TTL_SECONDS = 120L;

    private final RedisUtil redisUtil;

    /**
     * 生成重连令牌
     */
    public String createToken(Long userId, String roomId) {
        if (userId == null || !StringUtils.hasText(roomId)) {
            throw new IllegalArgumentException("userId 和 roomId 不能为空");
        }
        String token = UUID.randomUUID().toString().replace("-", "");
        String key = buildKey(token);
        Map<String, String> map = new HashMap<>();
        map.put("user_id", String.valueOf(userId));
        map.put("room_id", roomId);
        redisUtil.hSet(key, map);
        log.info("生成重连令牌(未启动TTL): userId={}, roomId={}, token={}", userId, roomId, token);
        return token;
    }

    public void startReconnectWindow(String token) {
        if (!StringUtils.hasText(token)) {
            return;
        }
        String key = buildKey(token);
        if (!redisUtil.hasKey(key)) {
            return;
        }
        long expiredAt = System.currentTimeMillis() + RECONNECT_TOKEN_TTL_SECONDS * 1000;
        redisUtil.hSet(key, "expired_at", String.valueOf(expiredAt), RECONNECT_TOKEN_TTL_SECONDS);
        log.info("启动重连窗口: token={}, ttl={}秒", token, RECONNECT_TOKEN_TTL_SECONDS);
    }

    /**
     * 校验重连令牌并返回 roomId
     */
    public String validate(String token, Long userId) {
        if (!StringUtils.hasText(token) || userId == null) {
            throw new BusinessException(BusinessErrorCode.WEBSOCKET_RECONNECT_TOKEN_INVALID, "重连令牌或用户信息缺失");
        }
        String key = buildKey(token);
        String storedUserId = redisUtil.hGet(key, "user_id");
        String roomId = redisUtil.hGet(key, "room_id");
        String expiredAtStr = redisUtil.hGet(key, "expired_at");

        if (!StringUtils.hasText(storedUserId) || !StringUtils.hasText(roomId)) {
            throw new BusinessException(BusinessErrorCode.WEBSOCKET_RECONNECT_TOKEN_INVALID, "重连令牌不存在或已失效");
        }

        if (!userId.toString().equals(storedUserId)) {
            throw new BusinessException(BusinessErrorCode.WEBSOCKET_RECONNECT_TOKEN_INVALID, "重连令牌与用户不匹配");
        }

        // 额外时间校验（Redis TTL 正常情况下即可保证，这里只是双保险）
        if (StringUtils.hasText(expiredAtStr)) {
            try {
                long expiredAt = Long.parseLong(expiredAtStr);
                if (Instant.now().toEpochMilli() > expiredAt) {
                    throw new BusinessException(BusinessErrorCode.WEBSOCKET_RECONNECT_TOKEN_INVALID, "重连令牌已过期");
                }
            } catch (NumberFormatException e) {
                log.warn("重连令牌 expired_at 字段格式异常: token={} value={}", token, expiredAtStr);
            }
        }

        log.info("重连令牌校验通过: userId={}, roomId={}, token={}", userId, roomId, token);

        // 校验通过后可选择删除令牌，避免重复使用
        redisUtil.delete(key);

        return roomId;
    }

    private String buildKey(String token) {
        return "ws:reconnect:" + token;
    }
}
